{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Customizing visual appearance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "HoloViews elements like the `Scatter` points illustrated in the [Introduction](1-Introduction.ipynb) contain two types of information:\n",
    "\n",
    "- **Your data**, in as close to its original form as possible, so that it can be analyzed and accessed as you see fit.\n",
    "- **Metadata specifying what your data *is***, which allows HoloViews to construct an appropriate visual representation for it.\n",
    "\n",
    "What elements do *not* contain is:\n",
    "\n",
    "- The endless details that one might want to tweak about the visual representation, such as line widths, colors, fonts, and spacing.\n",
    "\n",
    "HoloViews is designed to let you work naturally with the meaningful features of your data, while making it simple to adjust the display details separately using the Options system.  Among many other benefits, this [separation of *content* from *presentation*](https://en.wikipedia.org/wiki/Separation_of_content_and_presentation) simplifies your data analysis workflow, and makes it independent of any particular plotting backend."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Visualizing neural spike trains\n",
    "\n",
    "To illustrate how the options system works, we will use a dataset containing [\"spike\"](https://en.wikipedia.org/wiki/Action_potential) (neural firing) events extracted from the recorded electrical activity of a [neuron](https://en.wikipedia.org/wiki/Neuron). We will be visualizing the first trial of this [publicly accessible neural recording](http://www.neuralsignal.org/data/04/nsa2004.4/433l019). First, we import pandas and holoviews and load our data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import holoviews as hv\n",
    "from holoviews import opts\n",
    "\n",
    "spike_train = pd.read_csv('../assets/spike_train.csv.gz')\n",
    "spike_train.head(n=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This dataset contains the spike times (in milliseconds) for each detected spike event in this five-second recording, along with a spiking frequency in Hertz (spikes per second), averaged over a rolling 200 millisecond window. We will now declare ``Curve`` and ``Spike`` elements using this data and combine them into a ``Layout``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "curve  = hv.Curve( spike_train, 'milliseconds', 'Hertz', label='Firing Rate')\n",
    "spikes = hv.Spikes(spike_train, 'milliseconds', [],      label='Spike Train')\n",
    "\n",
    "layout = curve + spikes\n",
    "layout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that the representation for this object is purely textual; so far we have not yet loaded any plotting system for HoloViews, and so all you can see is a description of the data stored in the elements. \n",
    "\n",
    "To be able to see a visual representation and adjust its appearance, we'll need to load a plotting system, and here let's load two so they can be compared:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.extension('bokeh', 'matplotlib')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even though we can happily create, analyze, and manipulate HoloViews objects without using any plotting backend, this line is normally executed just after importing HoloViews so that objects can have a rich graphical representation rather than the very-limited textual representation shown above. Putting 'bokeh' first in this list makes visualizations default to using [Bokeh](http://bokeh.pydata.org), but including [matplotlib](http://matplotlib.org) as well means that backend can be selected for any particular plot as shown below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Default appearance\n",
    "\n",
    "With the extension loaded, let's look at the default appearance as rendered with Bokeh:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, we can immediately appreciate more about this dataset than we could from the textual representation.  The curve plot, in particular, conveys clearly that the firing rate varies quite a bit over this 5-second interval.  However, the spikes plot is much more difficult to interpret, because the plot is nearly solid black.  \n",
    "\n",
    "One thing we can do is click on one of the Bokeh plot's zoom tools to enable it, then zoom in until individual spikes are clearly visible.  Even then, though, it's difficult to relate the spiking and firing-rate representations to each other.  Maybe we can do better by adjusting the display options away from their default settings?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Customization\n",
    "\n",
    "Let's see what we can achieve when we do decide to customize the appearance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout.opts(\n",
    "    opts.Curve( height=200, width=900, xaxis=None, line_width=1.50, color='red', tools=['hover']),\n",
    "    opts.Spikes(height=150, width=900, yaxis=None, line_width=0.25, color='grey')).cols(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Much better! It's the same underlying data, but now we can clearly see both the individual spike events and how they affect the moving average.  You can also see how the moving average trails the actual spiking, due to how the window function was defined.\n",
    "\n",
    "A detailed breakdown of this exact customization is given in the [User Guide](../user_guide/03-Applying_Customization.ipynb), but we can use this example to understand a number of important concepts:\n",
    "\n",
    "* The options system is based around keyword settings supplied to the `.opts()` method.\n",
    "* Collections of keyword options can be built for a given element type using an \"options builder\" object, such as `opts.Curve` and `opts.Spikes` here, so that we can set options separately for each component of a composite object (as for height here)\n",
    "* Options builders also provide early *validation* of keywords (allowing errors to be detected even before the options are applied to an element) as well as *tab-completion* in IPython (try adding a comma to the `opts.Curve` or `opts.Spikes` keyword list to see what's available!).\n",
    "* The layout container has a ``cols`` method to specify the number of columns in the layout.\n",
    "\n",
    "The corresponding [User Guide](../user_guide/03-Applying_Customization.ipynb) entry explains the keywords used in detail, but a quick summary is that when you tab-complete using the `opts.*` builders, you are completing across two fundamental types of options: ***plot options*** (processed by HoloViews) and ***style options*** (processed by the underlying backend, either Bokeh or Matplotlib here). If you only use a single backend, you don't need to worry much about this distinction because HoloViews will ensure that the option setting is given to the appropriate backend when needed.  Here, for instance, the `color` and `line_width` keywords are not used by HoloViews; they will just be passed on to the corresponding [Bokeh glyphs](http://bokeh.pydata.org/en/latest/docs/user_guide/plotting.html). In this way you can control both HoloViews and the current backend, to customize almost any aspect of your plot."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Discovering options\n",
    "\n",
    "In the above cell, the result of calling `opts.Curve()` is passed into the `.opts` method returning an `Options` object. `opts.Curve()` and the other option builders aren't always needed, but the are very helpful for validating options and offer tab completion to help you discover possible values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dotted_options = opts.Curve(color='purple', width=600, height=250, line_dash='dotted')\n",
    "dotted_options"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try tab-completing the options for `Curve` above or specifying an invalid keyword. Now the `dotted_options` object can be passed to the `.opts` method call to customize a `Curve`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dotted = hv.Curve(spike_train, 'milliseconds', 'Hertz')\n",
    "dotted.opts(dotted_options)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When working directly with a single element, you can omit the options builder entirely because it's clear what type the options apply to:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dashed = hv.Curve( spike_train, 'milliseconds', 'Hertz')\n",
    "dashed.opts(color='orange', width=600, height=250, line_dash='dashed')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The code is then a bit shorter and more readable with the same result, but it no longer tab completes, and so omitting the builder is probably only useful for a final, published set of code, not during exploration. When using the `.opts` method on compositions of elements (i.e., layouts or overlays) you still need to use the options builders to indicate which type of object the options should be applied to. \n",
    "\n",
    "If you want to find out which options have been changed on a given object, you can use `.opts.info()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dashed.opts.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For more information on how to work with options, see the the [User Guide](../user_guide/03-Applying_Customization.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Switching to matplotlib\n",
    "\n",
    "Now let's customize our `layout` with options appropriate for the [Matplotlib](http://matplotlib.org) renderer, by supplying options associated with the matplotlib backend to the `.opts` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout = layout.opts(\n",
    "    opts.Curve( aspect=6, xaxis=None,   color='blue', linewidth=2, show_grid=False, \n",
    "               linestyle='dashed', backend='matplotlib'),\n",
    "    opts.Spikes(aspect=6, yaxis='bare', color='red',  linewidth=0.25, backend='matplotlib'),\n",
    "    opts.Layout(sublabel_format='', vspace=0.1, fig_size=200, backend='matplotlib'))\n",
    "layout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These options are now associated with matplotlib (due to `backend='matplotlib'`) even though the plot is still rendered with bokeh as we haven't switched to the matplotlib backend just yet (although matplotlib support was was loaded by `hv.extension` at the start of this notebook). The above code sets the options appropriate to matplotlib without immediately making use of them and naturally, a few changes needed to be made:\n",
    "\n",
    "* Some of the options are different because of differences in how the plotting backends work. For instance, matplotlib uses ``aspect`` instead of setting ``width`` and ``height``. In some cases, but not all, HoloViews can smooth over such differences in the *plotting* options to make it simpler to switch backends.\n",
    "* The Bokeh hover tool is not supported by the matplotlib backend, as you might expect, nor are there any other interactive controls, because the Matplotlib backend generates static PNG or SVG images.\n",
    "* Some options have different names; for instance, the Bokeh ``line_width`` option is called ``linewidth`` in matplotlib. These \"style\" options are directly inherited from the API of the plotting library backend, not defined by HoloViews.\n",
    "* Containers like `Layout`s also have some options to control the arrangement of its components. Here we adjust the gap betwen the plots using ``vspace``.\n",
    "\n",
    "Now we can use the `hv.output` utility to to show the same elements in `layout` as rendered with these different customizations, in a different output format (SVG), with a completely different plotting library:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.output(layout, backend='matplotlib', fig='svg')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This approach allows you to associate options for multiple different backends with the same object. See the [User Guide](../user_guide/03-Applying_Customization.ipynb) for more details, including information of how to use `hv.output` to affect global output settings."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Persistent styles\n",
    "\n",
    "Let's switch back to the default (Bokeh) plotting extension for this notebook and apply the ``.select`` operation illustrated in the Introduction, to the ``spikes`` object we made earlier:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.output(backend='bokeh')\n",
    "spikes.select(milliseconds=(2000,4000))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note how HoloViews remembered the Bokeh-specific styles we previously applied to the `spikes` object! This feature allows us to style objects once and then keep that styling as we work, without having to repeat the styles every time we work with that object. Note that even though this styling is associated with the element, it is not actually stored on it, which is mostly an implementation detail but does define a strict separation between what HoloViews considers parts of your data (the Element) and what is part of the \"look\" or the \"view\" of that data (the options associated with the object, but stored separately).  \n",
    "\n",
    "If we want to reset back to the original styling, we can call `.opts.clear()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "spikes.select(milliseconds=(2000,4000)).opts.clear()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can learn more about the output utility and how the options system handles persistent options in the [User Guide](../user_guide/03-Applying_Customization.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting axis labels\n",
    "\n",
    "If you look closely, the example above might worry you. First we defined our ``Spikes`` element with  ``kdims=['milliseconds']``, which we then used as a keyword argument in ``select`` above. This is also the string used as the axis label. Does this mean we are limited to Python identifiers for axis labels, if we want to use the corresponding dimension with ``select``?\n",
    "\n",
    "Luckily, there is no limitation involved.  Dimensions specified as strings are often convenient, but behind the scenes, HoloViews always uses a much richer ``Dimensions`` object that you can pass to the ``kdims`` and ``vdims`` explicitly (see the [User Guide](../user_guide/01-Annotating_Data.ipynb) for more information). One of the things each ``Dimension`` object supports is a long, descriptive ``label``,  which complements the short programmer-friendly name.\n",
    "\n",
    "We can set the dimension labels on our existing ``spikes`` object as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "spikes = spikes.redim.label(milliseconds='Time in milliseconds (10⁻³ seconds)')\n",
    "curve  = curve.redim.label(Hertz='Frequency (Hz)')\n",
    "(curve + spikes).select(milliseconds=(2000,4000)).cols(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, we can set long descriptive labels on our dimensions (including unicode) while still making use of the short dimension name in methods like ``select``.  \n",
    "\n",
    "Now that you know how to set up and customize basic visualizations, the next [Getting-Started sections](./3-Tabular_Datasets.ipynb) show how to work with various common types of data in HoloViews."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
